{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "2474e8ad-eaa6-4054-a3d3-dc0d8f21e10b",
   "metadata": {},
   "source": [
    "# Null Point Finder\n",
    "\n",
    "[Null Point Finder]: ../../api_static/plasmapy.analysis.nullpoint.rst\n",
    "\n",
    "The [null point finder] is functionality that is designed to find and analyze 3D magnetic null point locations and structures using a trilinear interpolation method as described in [Haynes et al. (2007)](https://doi.org/10.1063/1.2756751).\n",
    "\n",
    "This notebook covers how the [null point finder] utilizes trilinear interpolation in order to locate and classify the structures of magnetic null points. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9b3731e3-1936-4b26-8a05-2130669fd074",
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "from plasmapy.analysis import nullpoint\n",
    "\n",
    "plt.rcParams[\"figure.figsize\"] = [10.5, 0.56 * 10.5]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eec0e81c-bbf8-4e01-b121-114401996f7c",
   "metadata": {},
   "source": [
    "## Contents:\n",
    "\n",
    "1. [How the null point finder works](#How-the-null-point-finder-works)\n",
    "    1. [Locating a null point](#Locating-a-null-point)\n",
    "    1. [Classifying a null point](#Classifying-a-null-point)\n",
    "1. [Running through examples](#Running-through-examples)\n",
    "    1. [Uniform regular grid with a model function](#Uniform-regular-grid-with-a-model-function)\n",
    "    1. [Arbitrary regular grid](#Arbitrary-regular-grid)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60702e11-905b-43b3-85ab-dc3f85bc9bab",
   "metadata": {},
   "source": [
    "## How the null point finder works\n",
    "\n",
    "Null point finder provides two functions that can locate and classify null points in a magnetic field. The first function is [uniform_null_point_find()](../../api/plasmapy.analysis.nullpoint.uniform_null_point_find.rst)  and the second one is [null_point_find()](../../api/plasmapy.analysis.nullpoint.null_point_find.rst). As the names suggest, `uniform_null_point_find()` is used to locate and classify the magnetic null points of magnetic field located within a regular grid with uniform spacing in each dimension. It requires the user to provide the spacing between grid points in each of the three dimensions in addition to the minimum and maximum coordinate in each dimension. Moreover, it requires the user to provide a function which generates the vector values at a given point. `uniform_null_point_find()` is useful for when the user knows of such a function that accurately models the magnetic field vector at each point in space. \n",
    "On the other hand, `null_point_find()` is used when the user does not have an adequate modeling function. It also does not require the grid to have uniform spacing in each dimension. Instead, it will ask the user for three arrays (one for each dimension) of coordinates that determines the desired custom spacing in each of the three dimensions, and then constructs the resulting grid on its own. Furthermore, it requires the user to input all of the three components of magnetic field values, each as a 3D array with the same size as the grid, with each entry representing the strength of that component of the magnetic field for the corresponding grid coordinate. \n",
    "Finally, both functions take in as arguments two convergence thresholds that we will discuss later, for locating the null points. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72333e74-442c-4f7a-a3c9-76fad88eaac2",
   "metadata": {},
   "source": [
    "### Locating a null point\n",
    "\n",
    "Locating a null point is done via the trilinear analysis method discussed in the paper by [Haynes et al. (2007)](https://doi.org/10.1063/1.2756751). There are three steps that goes into locating the null points of a given regular grid. \n",
    "\n",
    "1. Reduction: First, every grid cell is checked for a simple condition so that we can rule out cells that cannot contain a null point.\n",
    "2. Trilinear Analysis: Assuming a trilinear field, the cells that have passed the reduction check are then analyzed, so that that we can be sure if they do contain a null point. \n",
    "3. Locating the null point: The cell that contains a null point is isolated, and the location of the null point is estimated using the iterative Newton-Raphson method with an initial random guess."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2bd7ab2d-427f-4d85-9d58-352aeffc0b52",
   "metadata": {},
   "source": [
    "### Classifying a null point\n",
    "\n",
    "Classification is done by analyzing the Jacobian matrix calculated at the location of null point. The full method is explained in the paper by [Parnell et al. (1996)](https://doi.org/10.1063/1.871810)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bdc1d3a6-35f3-4b6c-ac59-f81596db18ab",
   "metadata": {},
   "source": [
    "## Running through examples\n",
    "\n",
    "We will now run through an example for each of the two null point finding functions to see how to properly utilize the null point finder. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ebcced41-d08a-405e-9f7e-df89369bca9f",
   "metadata": {},
   "source": [
    "### Uniform regular grid with a model function\n",
    "\n",
    "First, let's define our modeling function for the magnetic field. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32d8553d-f114-4bc1-80d4-192070c06a01",
   "metadata": {},
   "outputs": [],
   "source": [
    "def magnetic_field(x, y, z):\n",
    "    return [(y - 1.5) * (z - 1.5), (x - 1.5) * (z - 1.5), (x - 1.5) * (y - 1.5)]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "035d413d-767c-499a-8424-cd5bd96dd375",
   "metadata": {},
   "source": [
    "The vector field defined above has a total of eight null points, located at $(\\pm 1.5, \\pm 1.5, \\pm 1.5)$. Now we will use `uniform_null_point_find()` to locate the null point with all positive components. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f3edf5bb-ef1f-4622-860b-9eacc95a20ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "nullpoint_args = {\n",
    "    \"x_range\": [1, 2],\n",
    "    \"y_range\": [1, 2],\n",
    "    \"z_range\": [1, 2],\n",
    "    \"precision\": [0.03, 0.03, 0.03],\n",
    "    \"func\": magnetic_field,\n",
    "}\n",
    "npoints = nullpoint.uniform_null_point_find(**nullpoint_args)\n",
    "print(npoints[0].loc)\n",
    "print(npoints[0].classification)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40b3c0c5-6155-45ae-b31e-6796655db09d",
   "metadata": {},
   "source": [
    "As we can see `uniform_null_point_find()` correctly identifies the location of the null point in addition to its type, which is a proper radial null."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b30dd51-d457-4a4b-b9d8-0471f13abb7e",
   "metadata": {},
   "source": [
    "### Arbitrary regular grid\n",
    "\n",
    "Now we will run through an example where the field components have to be directly provided by the user since an adequate modeling function is not given. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0f2834f2-ddfb-45a5-ab83-b14d8ac5a20f",
   "metadata": {},
   "outputs": [],
   "source": [
    "nullpoint2_args = {\n",
    "    \"x_arr\": [5, 6],\n",
    "    \"y_arr\": [5, 6],\n",
    "    \"z_arr\": [5, 6],\n",
    "    \"u_arr\": np.array([[[-0.5, -0.5], [0.5, 0.5]], [[-0.5, -0.5], [0.5, 0.5]]]),\n",
    "    \"v_arr\": np.array([[[-0.5, 0.5], [-0.5, 0.5]], [[-0.5, 0.5], [-0.5, 0.5]]]),\n",
    "    \"w_arr\": np.array([[[-0.5, -0.5], [-0.5, -0.5]], [[0.5, 0.5], [0.5, 0.5]]]),\n",
    "}\n",
    "npoints2 = nullpoint.null_point_find(**nullpoint2_args)\n",
    "print(npoints2[0].loc)\n",
    "print(npoints2[0].classification)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95cfcba7-2531-463b-88f5-40b60056602f",
   "metadata": {},
   "source": [
    "As we can see the magnetic field provided above has a spiral null point located at $(5.5,5.5,5.5)$."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
